﻿using CodeMonkey.EditableObject.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text;
using CommonLib.ExtendMethods;


namespace CodeMonkey.EditableObject.Core
{
    /// <summary>
    /// 用作通用的VM的基类,VM和底层的Model中不能有同名的属性
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    public class DynamicViewModel<TModel> : DynamicViewModelBase<TModel> where TModel : class
    {
        #region ctor
        public DynamicViewModel(TModel model)
            : base(model)
        {
        }
        #endregion
    }

    /// <summary>
    /// 专用于编辑的基类,VM和底层的Model中不能有同名的属性
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    public abstract class EditableDynamicViewModel<TModel> : DynamicViewModelBase<TModel>, IEditableObject, IDataErrorInfo where TModel : class
    {
        /// <summary>
        /// 保存的对应PropertyInfo的值都是原始类型
        /// </summary>
        private Dictionary<PropertyInfo, object> modifyedPropValuePairs;

        #region ctor
        public EditableDynamicViewModel(TModel model)
            : base(model)
        {
            modifyedPropValuePairs = new Dictionary<PropertyInfo, object>();
        }

        public EditableDynamicViewModel(DynamicViewModel<TModel> dynamicVM)
            : this(dynamicVM.ModelInstance)
        {
            this.ModelInstance = dynamicVM.ModelInstance;
        }
        #endregion

        #region Properties
        public bool IsModifyed
        {
            get
            {
                return modifyedPropValuePairs.Count > 0;
            }
        }

        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
        #region Dynamic capabilities
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// 该方法会在某个实际上不存在于该VM中的属性被读取时调用.也就是说如果如果该属性被定义在该VM中,那么属性的读取将直接进行,,,而不是通过此方法来进行
        /// </summary>
        /// <param name="binder">Provides information about the object that called the dynamic operation.binder.Name为被操作的属性的属性名</param>
        /// <param name="result">如果underlying model instance有与binder.Name相匹配的,可读的属性,,,则返回true,,,否则返回false</param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (this.ModelInstance == null)
            {
                throw new InvalidOperationException("This view-model instance has already been disposed; it no longer supports 'get' operations");
            }

            string propertyName = binder.Name;
            Contract.Assume(propertyName != null, "The binder property name should never be null");

            if (TryGetModifyedPropertyValue(propertyName, out result))
            {
                return true;
            }
            else
            {
                bool isPropertyInModelAndCanRead = TryGetPropertyValueFromModelInstance(propertyName, out result);
                return isPropertyInModelAndCanRead;
            }
        }

        /*外界调用基类定义的NotifyUI方法时,如果进行通知的属性中包含了集合,,,比如:
         * 进行选择性别的枚举集合,,,那么该通知将导致VM中对应的Sexy属性被更新,,然后TrySetMember就会进行设置,,,而设置的值是null,这就导致了紧接下会发生的TryGetMember方法中的读取修改值时的异常(即TryGetModifyedPropertyValue这个方法里的ToString())*/
        /// <summary>
        /// 如果进行设置的属性不存在于VM中,则该方法被调用.
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value">如果underlying model instance有与binder.Name相匹配的,可写的属性,,,则返回true,,,否则返回false</param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (this.ModelInstance == null)
            {
                throw new InvalidOperationException("This view-model instance has already been disposed; it no longer supports 'get' operations");
            }

            // Somebody is trying to set a property named binder.Name on the view-model.//能执行到这里说明:某个操作正在试图设置VM中binder.Name指示的属性的属性值
            // No such statically defined property exists (or it would have been called rather than this method)
            // so this method will automatically delegate the "set property" to the underlying model instance
            string propertyName = binder.Name;
            Contract.Assume(string.IsNullOrWhiteSpace(propertyName) == false, "The binder property name should never be ");

            PropertyInfo propInfo = this.ModelInstance.GetType().GetProperty(propertyName);
            if (propInfo == null || propInfo.CanWrite == false)
            {
                return false;
            }

            modifyedPropValuePairs[propInfo] = value;
            // Set the value of the underlying model property ,,,不再设置进Model中
            //property.SetValue(this.ModelInstance, value, null);

            // Execute the common setter functionality on the property
            // This includes raising events and processing other affected properties
            //负责触发属性通知(包括关联属性)
            //编辑时就不触发通知了
            //this.CommonSetterFunctionality(propInfo);

            return true;
        }

        #endregion

        #region  IDataErrorInfo Members
        public string Error
        {
            get
            {
                string totalError = string.Empty;
                Array.ForEach(ModelInstance.GetType().GetProperties(), item => totalError += this[item.Name]);
                return totalError;
            }
        }

        public string this[string columnName]
        {
            get { return ValidateProperty(columnName); }
        }

        // abstract method ; provide to subclass for  IDataErrorInfo Interface
        protected abstract string ValidateProperty(string prop);
        #endregion

        #region IEditableObject Members

        public void BeginEdit()
        {
            modifyedPropValuePairs.Clear();
        }

        /// <summary>
        /// 取消编辑界面的变化
        /// </summary>
        public void CancelEdit()
        {
            var properties = new PropertyInfo[modifyedPropValuePairs.Keys.Count];
            modifyedPropValuePairs.Keys.CopyTo(properties, 0);

            modifyedPropValuePairs.Clear();

            foreach (var property in properties) //让编辑界面上的属性值归位
            {
                RaisePropertyChanged(property.Name);
            }
        }

        //该方法被调用时;代表modifyedPropValuePairs存储的都是合法值;即值类型不会出现无法转换的问题
        public void EndEdit()
        {
            EndEditImpl(true);
        }

        //DataGrid在行编辑模式下,,,如果验证通过,在退出编辑模式时会调用这个函数...原因已查明
        //能执行这个;说明所有的数据都已经至少可以正确转换
        /// <param name="notify">为false;代表是要获取传递给数据库或服务端的数据...为true;则代表需要更新本地值来反应到UI上</param>
        void EndEditImpl(bool notify)
        {
            var propInfoValuePairsInGenericType = modifyedPropValuePairs.Where(propertyValuePair => propertyValuePair.Key.DeclaringType == typeof(TModel));
            foreach (var propInfoValuePair in propInfoValuePairsInGenericType)
            {
                SetValueToProperty(propInfoValuePair);
            }

            //TODO 这个通知可有可无,,,因为如果使用这个方式..则表明被修改之后的数据要传递给服务器或存到数据库,,,
            //这个通知功能必须被
            //if (notify)
            //{
            //    foreach (var propertyValue in propInfoValuePairsInGenericType)
            //    {
            //        RaisePropertyChanged(propertyValue.Key.Name);
            //    }
            //}

            modifyedPropValuePairs.Clear();
        }

        void SetValueToProperty(KeyValuePair<PropertyInfo, object> propInfoValuePair)
        {
            PropertyInfo propInfo = propInfoValuePair.Key;
            object valueMatch = null;
            if (propInfo.PropertyType.IsDigit()) //如果为数值类型
            {
                valueMatch = Convert.ChangeType(propInfoValuePair.Value, propInfo.PropertyType);
            }
            else
            {
                valueMatch = propInfoValuePair.Value;
            }
            propInfo.SetValue(ModelInstance, valueMatch, null);

        }
        #endregion

        #region  Help Methods

        // 用于从T类型中获取参数propName指示的属性的值
        /// <summary>
        /// 作验证的时候;一律将需要验证的属性的TProperty设置为string;
        /// 其它作为基准的属性类型依然设置为原有类型
        /// </summary>
        protected TProperty GetPropertyValue<TProperty>(string propName)
        {
            /*  //可以主观能感知到性能缺失...但是可以通过引入Microsoft.VisualBasic.dll来提升近乎 20×的速度,,能在主观上达到下面利用反射达到的速度
            object val = (string)GetProperty(this, propName);
            return (TProperty)Convert.ChangeType(val, typeof(TProperty));
            */
            object result;
            bool  isPropHasModified = TryGetModifyedPropertyValue(propName, out result);
            if (!isPropHasModified)
            {
                TryGetPropertyValueFromModelInstance(propName, out result);
            }
            return (TProperty)Convert.ChangeType(result, typeof(TProperty));
        }

        bool TryGetModifyedPropertyValue(string propName, out object value)
        {
            foreach (KeyValuePair<PropertyInfo, object> propInfoValuePair in modifyedPropValuePairs)
            {
                if (string.Equals(propInfoValuePair.Key.Name, propName))
                {
                    //value = propInfoValuePair.Value;
                    value = propInfoValuePair.Value.ToString();//都以字符形式返回
                    return true;
                }
            }

            value = null;
            return false;
        }

        bool TryGetPropertyValueFromModelInstance(string propName, out object value)
        {
            PropertyInfo property = this.ModelInstance.GetType().GetProperty(propName);

            if (property == null || property.CanRead == false)
            {
                value = null;
                return false;
            }//if - no such property on the model

            // Return the value of the underlying model property
            value = property.GetValue(this.ModelInstance, null).ToString();//,,都以字符形式返回
            return true;
        }

        #region  backup
        private static object GetProperty(object target, string name)
        {
            System.Runtime.CompilerServices.CallSiteBinder callSiteBinder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, name, target.GetType(), new[] { Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(0, null) });
            var callSite = System.Runtime.CompilerServices.CallSite<Func<System.Runtime.CompilerServices.CallSite, object, object>>.Create(callSiteBinder);
            return callSite.Target(callSite, target);//根据跟踪,这一句会去调用上面重写的TryGetMember方法
        }
        ////需要引入Microsoft.VisualBasic.dll
        //public static object GetProperty(object target, string name)
        //{
        //    return Microsoft.VisualBasic.CompilerServices.Versioned.CallByName(target, name, Microsoft.VisualBasic.CallType.Get);
        //}
        #endregion

        #endregion
    }
}
